"""
重复值处理案例
演示重复值检测和处理方法
"""

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from datetime import datetime, timedelta
import warnings
warnings.filterwarnings('ignore')

# 设置中文显示
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS']
plt.rcParams['axes.unicode_minus'] = False

print("=" * 80)
print("重复值处理案例")
print("=" * 80)

# ============================================================================
# 步骤1：创建带重复值的示例数据
# ============================================================================

print("\n" + "=" * 80)
print("步骤1：创建带重复值的示例数据")
print("=" * 80)

np.random.seed(42)

# 创建基础数据
n_samples = 500
base_data = {
    'user_id': np.random.randint(1000, 2000, n_samples),
    'name': np.random.choice(['张三', '李四', '王五', '赵六', '钱七'], n_samples),
    'age': np.random.randint(20, 60, n_samples),
    'city': np.random.choice(['北京', '上海', '广州', '深圳'], n_samples),
    'purchase_amount': np.random.uniform(100, 5000, n_samples),
    'purchase_date': [datetime(2024, 1, 1) + timedelta(days=int(x)) 
                      for x in np.random.randint(0, 365, n_samples)]
}

df = pd.DataFrame(base_data)

# 添加完全重复的行
duplicate_indices = np.random.choice(df.index, 50, replace=False)
df_duplicates = df.loc[duplicate_indices].copy()
df = pd.concat([df, df_duplicates], ignore_index=True)

# 添加部分重复（相同user_id和name，但其他信息不同）
partial_dup_indices = np.random.choice(df.index, 30, replace=False)
df_partial = df.loc[partial_dup_indices].copy()
df_partial['purchase_amount'] = np.random.uniform(100, 5000, len(df_partial))
df_partial['purchase_date'] = [datetime(2024, 1, 1) + timedelta(days=int(x))
                                for x in np.random.randint(0, 365, len(df_partial))]
df = pd.concat([df, df_partial], ignore_index=True)

# 打乱顺序
df = df.sample(frac=1, random_state=42).reset_index(drop=True)

print(f"数据集形状: {df.shape}")
print(f"\n数据预览：")
print(df.head(10))

# ============================================================================
# 步骤2：重复值检测
# ============================================================================

print("\n" + "=" * 80)
print("步骤2：重复值检测")
print("=" * 80)

# 2.1 检测完全重复
print("\n2.1 检测完全重复")
print("-" * 80)

# 检测重复行
duplicates = df.duplicated()
print(f"重复行数量: {duplicates.sum()}")
print(f"重复比例: {duplicates.sum() / len(df) * 100:.2f}%")

# 查看重复的行
print(f"\n重复行示例（前5条）：")
duplicate_rows = df[df.duplicated(keep=False)].sort_values('user_id')
print(duplicate_rows.head(10))

# 2.2 检测特定列的重复
print("\n2.2 检测特定列的重复")
print("-" * 80)

# 检测user_id和name的重复
key_duplicates = df.duplicated(subset=['user_id', 'name'])
print(f"基于[user_id, name]的重复行数: {key_duplicates.sum()}")
print(f"重复比例: {key_duplicates.sum() / len(df) * 100:.2f}%")

# 查看这些重复
print(f"\n基于[user_id, name]的重复示例：")
key_dup_rows = df[df.duplicated(subset=['user_id', 'name'], keep=False)].sort_values(['user_id', 'name'])
print(key_dup_rows.head(10))

# 2.3 统计每个键的重复次数
print("\n2.3 统计每个键的重复次数")
print("-" * 80)

duplicate_counts = df.groupby(['user_id', 'name']).size()
duplicate_counts = duplicate_counts[duplicate_counts > 1].sort_values(ascending=False)
print(f"\n重复次数最多的前10个用户：")
print(duplicate_counts.head(10))

# 2.4 识别重复类型
print("\n2.4 识别重复类型")
print("-" * 80)

# 完全重复
complete_dup = df[df.duplicated(keep=False)]
complete_dup_count = len(complete_dup)

# 部分重复（相同user_id和name，但其他列不同）
key_dup = df[df.duplicated(subset=['user_id', 'name'], keep=False)]
partial_dup_count = len(key_dup) - complete_dup_count

print(f"完全重复: {complete_dup_count} 行")
print(f"部分重复: {partial_dup_count} 行")
print(f"唯一记录: {len(df) - len(key_dup)} 行")

# ============================================================================
# 步骤3：可视化重复值
# ============================================================================

print("\n" + "=" * 80)
print("步骤3：可视化重复值")
print("=" * 80)

fig, axes = plt.subplots(2, 2, figsize=(16, 10))
fig.suptitle('重复值分析', fontsize=16, fontweight='bold')

# 重复值分布
dup_stats = pd.DataFrame({
    '类型': ['唯一记录', '完全重复', '部分重复'],
    '数量': [len(df) - len(key_dup), complete_dup_count, partial_dup_count]
})
axes[0, 0].pie(dup_stats['数量'], labels=dup_stats['类型'], autopct='%1.1f%%',
               colors=['lightgreen', 'coral', 'skyblue'])
axes[0, 0].set_title('重复值分布', fontweight='bold')

# 重复次数分布
dup_freq = df.groupby(['user_id', 'name']).size()
axes[0, 1].hist(dup_freq, bins=range(1, dup_freq.max() + 2), edgecolor='black', alpha=0.7)
axes[0, 1].set_xlabel('重复次数')
axes[0, 1].set_ylabel('频数')
axes[0, 1].set_title('重复次数分布', fontweight='bold')
axes[0, 1].grid(True, alpha=0.3, axis='y')

# Top 10 重复最多的用户
top_duplicates = duplicate_counts.head(10)
axes[1, 0].barh(range(len(top_duplicates)), top_duplicates.values, color='coral')
axes[1, 0].set_yticks(range(len(top_duplicates)))
axes[1, 0].set_yticklabels([f"User {idx[0]}" for idx in top_duplicates.index])
axes[1, 0].set_xlabel('重复次数')
axes[1, 0].set_title('重复次数最多的10个用户', fontweight='bold')
axes[1, 0].grid(True, alpha=0.3, axis='x')

# 按城市统计重复
city_duplicates = df[df.duplicated(subset=['user_id', 'name'], keep=False)].groupby('city').size()
axes[1, 1].bar(city_duplicates.index, city_duplicates.values, color='skyblue', edgecolor='black')
axes[1, 1].set_xlabel('城市')
axes[1, 1].set_ylabel('重复记录数')
axes[1, 1].set_title('各城市重复记录分布', fontweight='bold')
axes[1, 1].grid(True, alpha=0.3, axis='y')

plt.tight_layout()
plt.savefig('/Users/binming/Desktop/CodeBase/python/机器学习/特征工程/0-数据清洗/重复值分析.png',
            dpi=300, bbox_inches='tight')
print("重复值分析图已保存")

# ============================================================================
# 步骤4：重复值处理方法
# ============================================================================

print("\n" + "=" * 80)
print("步骤4：重复值处理方法")
print("=" * 80)

# 4.1 删除完全重复（保留第一次出现）
print("\n4.1 删除完全重复（保留第一次出现）")
print("-" * 80)

df_drop_first = df.drop_duplicates(keep='first')
print(f"原始数据: {len(df)} 行")
print(f"删除后: {len(df_drop_first)} 行")
print(f"删除数量: {len(df) - len(df_drop_first)} 行")

# 4.2 删除完全重复（保留最后一次出现）
print("\n4.2 删除完全重复（保留最后一次出现）")
print("-" * 80)

df_drop_last = df.drop_duplicates(keep='last')
print(f"原始数据: {len(df)} 行")
print(f"删除后: {len(df_drop_last)} 行")
print(f"删除数量: {len(df) - len(df_drop_last)} 行")

# 4.3 删除所有重复（包括第一次出现）
print("\n4.3 删除所有重复（包括第一次出现）")
print("-" * 80)

df_drop_all = df.drop_duplicates(keep=False)
print(f"原始数据: {len(df)} 行")
print(f"删除后: {len(df_drop_all)} 行")
print(f"删除数量: {len(df) - len(df_drop_all)} 行")

# 4.4 基于特定列删除重复
print("\n4.4 基于特定列删除重复")
print("-" * 80)

df_drop_subset = df.drop_duplicates(subset=['user_id', 'name'], keep='first')
print(f"原始数据: {len(df)} 行")
print(f"删除后: {len(df_drop_subset)} 行")
print(f"删除数量: {len(df) - len(df_drop_subset)} 行")

# 4.5 聚合重复记录
print("\n4.5 聚合重复记录")
print("-" * 80)

# 对于同一用户的多次购买，计算总购买金额
df_aggregated = df.groupby(['user_id', 'name']).agg({
    'age': 'first',  # 取第一个值
    'city': 'first',
    'purchase_amount': 'sum',  # 求和
    'purchase_date': 'max'  # 取最新日期
}).reset_index()

print(f"原始数据: {len(df)} 行")
print(f"聚合后: {len(df_aggregated)} 行")
print(f"\n聚合后数据示例：")
print(df_aggregated.head())

# 4.6 保留特定条件的记录
print("\n4.6 保留特定条件的记录（如最大购买金额）")
print("-" * 80)

# 对每个用户，保留购买金额最大的记录
df_max_purchase = df.loc[df.groupby(['user_id', 'name'])['purchase_amount'].idxmax()]
print(f"原始数据: {len(df)} 行")
print(f"筛选后: {len(df_max_purchase)} 行")
print(f"\n筛选后数据示例：")
print(df_max_purchase.head())

# 4.7 标记重复
print("\n4.7 标记重复（添加重复标记列）")
print("-" * 80)

df_marked = df.copy()
df_marked['is_duplicate'] = df.duplicated(subset=['user_id', 'name'], keep='first')
df_marked['duplicate_count'] = df.groupby(['user_id', 'name'])['user_id'].transform('count')

print(f"添加了重复标记列")
print(f"\n标记后数据示例：")
print(df_marked[df_marked['is_duplicate']].head())

# ============================================================================
# 步骤5：高级重复检测
# ============================================================================

print("\n" + "=" * 80)
print("步骤5：高级重复检测（模糊匹配）")
print("=" * 80)

# 5.1 基于相似度的重复检测
print("\n5.1 检测近似重复（示例：名字相似）")
print("-" * 80)

# 创建一些名字相似的数据
similar_names = pd.DataFrame({
    'name': ['张三', '张 三', '张三 ', '李四', '李四', '王五'],
    'value': [100, 100, 100, 200, 200, 300]
})

print("原始数据：")
print(similar_names)

# 去除空格后检测重复
similar_names['name_clean'] = similar_names['name'].str.strip()
duplicates_clean = similar_names.duplicated(subset=['name_clean'])
print(f"\n清理空格后的重复: {duplicates_clean.sum()} 行")

# 5.2 时间窗口内的重复
print("\n5.2 时间窗口内的重复检测")
print("-" * 80)

# 检测同一用户在7天内的重复购买
df_sorted = df.sort_values(['user_id', 'purchase_date'])
df_sorted['days_since_last'] = df_sorted.groupby('user_id')['purchase_date'].diff().dt.days

# 标记7天内的重复购买
df_sorted['is_recent_duplicate'] = (df_sorted['days_since_last'] <= 7) & (df_sorted['days_since_last'].notna())
recent_duplicates = df_sorted['is_recent_duplicate'].sum()

print(f"7天内重复购买的记录: {recent_duplicates} 行")
print(f"\n示例：")
print(df_sorted[df_sorted['is_recent_duplicate']][['user_id', 'name', 'purchase_date', 'days_since_last']].head())

# ============================================================================
# 步骤6：对比不同处理方法
# ============================================================================

print("\n" + "=" * 80)
print("步骤6：对比不同处理方法")
print("=" * 80)

methods_comparison = pd.DataFrame({
    '处理方法': [
        '保留第一次',
        '保留最后一次',
        '删除所有重复',
        '基于关键列',
        '聚合',
        '保留最大值'
    ],
    '剩余行数': [
        len(df_drop_first),
        len(df_drop_last),
        len(df_drop_all),
        len(df_drop_subset),
        len(df_aggregated),
        len(df_max_purchase)
    ],
    '删除行数': [
        len(df) - len(df_drop_first),
        len(df) - len(df_drop_last),
        len(df) - len(df_drop_all),
        len(df) - len(df_drop_subset),
        len(df) - len(df_aggregated),
        len(df) - len(df_max_purchase)
    ]
})

print("\n不同处理方法对比：")
print(methods_comparison.to_string(index=False))

# 可视化对比
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# 剩余行数对比
axes[0].barh(methods_comparison['处理方法'], methods_comparison['剩余行数'], color='lightgreen')
axes[0].set_xlabel('剩余行数')
axes[0].set_title('不同处理方法的剩余行数', fontweight='bold')
axes[0].grid(True, alpha=0.3, axis='x')

# 删除行数对比
axes[1].barh(methods_comparison['处理方法'], methods_comparison['删除行数'], color='coral')
axes[1].set_xlabel('删除行数')
axes[1].set_title('不同处理方法的删除行数', fontweight='bold')
axes[1].grid(True, alpha=0.3, axis='x')

plt.tight_layout()
plt.savefig('/Users/binming/Desktop/CodeBase/python/机器学习/特征工程/0-数据清洗/重复值处理对比.png',
            dpi=300, bbox_inches='tight')
print("\n重复值处理对比图已保存")

# ============================================================================
# 步骤7：最佳实践建议
# ============================================================================

print("\n" + "=" * 80)
print("步骤7：重复值处理最佳实践")
print("=" * 80)

print("""
┌─────────────────┬──────────────────────┬─────────────────────┬──────────────────┐
│   重复类型      │      处理方法        │        适用场景     │      注意事项    │
├─────────────────┼──────────────────────┼─────────────────────┼──────────────────┤
│ 完全重复        │ 删除（keep='first'） │ 数据录入错误        │ 确认是错误数据   │
│                 │                      │                     │                  │
├─────────────────┼──────────────────────┼─────────────────────┼──────────────────┤
│ 部分重复        │ 聚合                 │ 交易记录            │ 选择合适聚合函数 │
│ (同一实体)      │                      │ 时间序列数据        │                  │
├─────────────────┼──────────────────────┼─────────────────────┼──────────────────┤
│ 业务重复        │ 保留特定条件         │ 保留最新/最大值     │ 结合业务逻辑     │
│                 │                      │                     │                  │
├─────────────────┼──────────────────────┼─────────────────────┼──────────────────┤
│ 时间重复        │ 时间窗口过滤         │ 用户行为分析        │ 定义合理窗口     │
│                 │                      │                     │                  │
├─────────────────┼──────────────────────┼─────────────────────┼──────────────────┤
│ 模糊重复        │ 清洗+标准化          │ 文本数据            │ 需要人工审核     │
│                 │                      │                     │                  │
└─────────────────┴──────────────────────┴─────────────────────┴──────────────────┘

处理流程：

1. **识别重复类型**
   - 完全重复：所有列相同
   - 部分重复：关键列相同，其他列不同
   - 业务重复：根据业务规则判断

2. **分析重复原因**
   - 数据录入错误
   - 系统重复提交
   - 合理的多次记录
   - 数据合并导致

3. **选择处理策略**
   - 错误数据：删除
   - 多次记录：聚合或保留最新
   - 不确定：标记后人工审核

4. **验证处理结果**
   - 检查数据量变化
   - 验证业务逻辑
   - 对比处理前后统计

常用方法：

# 删除完全重复
df.drop_duplicates()

# 基于特定列删除
df.drop_duplicates(subset=['key_col'])

# 聚合
df.groupby('key').agg({'col1': 'sum', 'col2': 'mean'})

# 保留最新
df.sort_values('date').drop_duplicates(subset=['key'], keep='last')

# 保留最大值
df.loc[df.groupby('key')['value'].idxmax()]

关键要点：
✓ 先理解重复的业务含义
✓ 区分错误重复和合理重复
✓ 记录处理决策和规则
✓ 保留原始数据备份
✓ 验证处理结果的合理性
✓ 文档化重复处理逻辑
""")

print("\n" + "=" * 80)
print("案例完成！")
print("=" * 80)
